1   /*
2    * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
26  
27  import java.util.*;
28  import java.util.jar.*;
29  import java.lang.reflect.*;
30  import java.io.*;
31  import xmlkit.XMLKit.Element;
32  
33  /*
34   * @author jrose
35   */
36  public class ClassReader extends ClassSyntax {
37  
38      private static final CommandLineParser CLP = new CommandLineParser(""
39              + "-source:     +> = \n"
40              + "-dest:       +> = \n"
41              + "-encoding:   +> = \n"
42              + "-jcov           $ \n   -nojcov         !-jcov        \n"
43              + "-verbose        $ \n   -noverbose      !-verbose     \n"
44              + "-pretty         $ \n   -nopretty       !-pretty      \n"
45              + "-keepPath       $ \n   -nokeepPath     !-keepPath    \n"
46              + "-keepCP         $ \n   -nokeepCP       !-keepCP      \n"
47              + "-keepBytes      $ \n   -nokeepBytes    !-keepBytes   \n"
48              + "-parseBytes     $ \n   -noparseBytes   !-parseBytes  \n"
49              + "-resolveRefs    $ \n   -noresolveRefs  !-resolveRefs \n"
50              + "-keepOrder      $ \n   -nokeepOrder    !-keepOrder   \n"
51              + "-keepSizes      $ \n   -nokeepSizes    !-keepSizes   \n"
52              + "-continue       $ \n   -nocontinue     !-continue    \n"
53              + "-attrDef        & \n"
54              + "-@         >-@  . \n"
55              + "-              +? \n"
56              + "\n");
57  
58      public static void main(String[] ava) throws IOException {
59          ArrayList<String> av = new ArrayList<String>(Arrays.asList(ava));
60          HashMap<String, String> props = new HashMap<String, String>();
61          props.put("-encoding:", "UTF8");  // default
62          props.put("-keepOrder", null);    // CLI default
63          props.put("-pretty", "1");     // CLI default
64          props.put("-continue", "1");     // CLI default
65          CLP.parse(av, props);
66          //System.out.println(props+" ++ "+av);
67          File source = asFile(props.get("-source:"));
68          File dest = asFile(props.get("-dest:"));
69          String encoding = props.get("-encoding:");
70          boolean contError = props.containsKey("-continue");
71          ClassReader options = new ClassReader();
72          options.copyOptionsFrom(props);
73          /*
74          if (dest == null && av.size() > 1) {
75          dest = File.createTempFile("TestOut", ".dir", new File("."));
76          dest.delete();
77          if (!dest.mkdir())
78          throw new RuntimeException("Cannot create "+dest);
79          System.out.println("Writing results to "+dest);
80          }
81           */
82          if (av.isEmpty()) {
83              av.add("doit");  //to enter this loop
84          }
85          boolean readList = false;
86          for (String a : av) {
87              if (readList) {
88                  readList = false;
89                  InputStream fin;
90                  if (a.equals("-")) {
91                      fin = System.in;
92                  } else {
93                      fin = new FileInputStream(a);
94                  }
95  
96                  BufferedReader files = makeReader(fin, encoding);
97                  for (String file; (file = files.readLine()) != null;) {
98                      doFile(file, source, dest, options, encoding, contError);
99                  }
100                 if (fin != System.in) {
101                     fin.close();
102                 }
103             } else if (a.equals("-@")) {
104                 readList = true;
105             } else if (a.startsWith("-")) {
106                 throw new RuntimeException("Bad flag argument: " + a);
107             } else if (source.getName().endsWith(".jar")) {
108                 doJar(a, source, dest, options, encoding, contError);
109             } else {
110                 doFile(a, source, dest, options, encoding, contError);
111             }
112         }
113     }
114 
115     private static File asFile(String str) {
116         return (str == null) ? null : new File(str);
117     }
118 
119     private static void doFile(String a,
120             File source, File dest,
121             ClassReader options, String encoding,
122             boolean contError) throws IOException {
123         if (!contError) {
124             doFile(a, source, dest, options, encoding);
125         } else {
126             try {
127                 doFile(a, source, dest, options, encoding);
128             } catch (Exception ee) {
129                 System.out.println("Error processing " + source + ": " + ee);
130             }
131         }
132     }
133 
134     private static void doJar(String a, File source, File dest, ClassReader options,
135             String encoding, Boolean contError) throws IOException {
136         try {
137             JarFile jf = new JarFile(source);
138             for (JarEntry je : Collections.list((Enumeration<JarEntry>) jf.entries())) {
139                 String name = je.getName();
140                 if (!name.endsWith(".class")) {
141                     continue;
142                 }
143                 doStream(name, jf.getInputStream(je), dest, options, encoding);
144             }
145         } catch (IOException ioe) {
146             if (contError) {
147                 System.out.println("Error processing " + source + ": " + ioe);
148             } else {
149                 throw ioe;
150             }
151         }
152     }
153 
154     private static void doStream(String a, InputStream in, File dest,
155             ClassReader options, String encoding) throws IOException {
156 
157         File f = new File(a);
158         ClassReader cr = new ClassReader(options);
159         Element e = cr.readFrom(in);
160 
161         OutputStream out;
162         if (dest == null) {
163             //System.out.println(e.prettyString());
164             out = System.out;
165         } else {
166             File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath());
167             String outName = outf.getName();
168             File outSubdir = outf.getParentFile();
169             outSubdir.mkdirs();
170             int extPos = outName.lastIndexOf('.');
171             if (extPos > 0) {
172                 outf = new File(outSubdir, outName.substring(0, extPos) + ".xml");
173             }
174             out = new FileOutputStream(outf);
175         }
176 
177         Writer outw = makeWriter(out, encoding);
178         if (options.pretty || !options.keepOrder) {
179             e.writePrettyTo(outw);
180         } else {
181             e.writeTo(outw);
182         }
183         if (out == System.out) {
184             outw.write("\n");
185             outw.flush();
186         } else {
187             outw.close();
188         }
189     }
190 
191     private static void doFile(String a,
192             File source, File dest,
193             ClassReader options, String encoding) throws IOException {
194         File inf = new File(source, a);
195         if (dest != null && options.verbose) {
196             System.out.println("Reading " + inf);
197         }
198 
199         BufferedInputStream in = new BufferedInputStream(new FileInputStream(inf));
200 
201         doStream(a, in, dest, options, encoding);
202 
203     }
204 
205     public static BufferedReader makeReader(InputStream in, String encoding) throws IOException {
206         // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name
207         if (encoding.equals("8BIT")) {
208             encoding = EIGHT_BIT_CHAR_ENCODING;
209         }
210         if (encoding.equals("UTF8")) {
211             encoding = UTF8_ENCODING;
212         }
213         if (encoding.equals("DEFAULT")) {
214             encoding = null;
215         }
216         if (encoding.equals("-")) {
217             encoding = null;
218         }
219         Reader inw;
220         in = new BufferedInputStream(in);  // add buffering
221         if (encoding == null) {
222             inw = new InputStreamReader(in);
223         } else {
224             inw = new InputStreamReader(in, encoding);
225         }
226         return new BufferedReader(inw);  // add buffering
227     }
228 
229     public static Writer makeWriter(OutputStream out, String encoding) throws IOException {
230         // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name
231         if (encoding.equals("8BIT")) {
232             encoding = EIGHT_BIT_CHAR_ENCODING;
233         }
234         if (encoding.equals("UTF8")) {
235             encoding = UTF8_ENCODING;
236         }
237         if (encoding.equals("DEFAULT")) {
238             encoding = null;
239         }
240         if (encoding.equals("-")) {
241             encoding = null;
242         }
243         Writer outw;
244         if (encoding == null) {
245             outw = new OutputStreamWriter(out);
246         } else {
247             outw = new OutputStreamWriter(out, encoding);
248         }
249         return new BufferedWriter(outw);  // add buffering
250     }
251 
252     public Element result() {
253         return cfile;
254     }
255     protected InputStream in;
256     protected ByteArrayOutputStream buf = new ByteArrayOutputStream(1024);
257     protected byte cpTag[];
258     protected String cpName[];
259     protected String[] callables;     // varies
260     public static final String REF_PREFIX = "#";
261     // input options
262     public boolean pretty = false;
263     public boolean verbose = false;
264     public boolean keepPath = false;
265     public boolean keepCP = false;
266     public boolean keepBytes = false;
267     public boolean parseBytes = true;
268     public boolean resolveRefs = true;
269     public boolean keepOrder = true;
270     public boolean keepSizes = false;
271 
272     public ClassReader() {
273         super.cfile = new Element("ClassFile");
274     }
275 
276     public ClassReader(ClassReader options) {
277         this();
278         copyOptionsFrom(options);
279     }
280 
281     public void copyOptionsFrom(ClassReader options) {
282         pretty = options.pretty;
283         verbose = options.verbose;
284         keepPath = options.keepPath;
285         keepCP = options.keepCP;
286         keepBytes = options.keepBytes;
287         parseBytes = options.parseBytes;
288         resolveRefs = options.resolveRefs;
289         keepSizes = options.keepSizes;
290         keepOrder = options.keepOrder;
291         attrTypes = options.attrTypes;
292     }
293 
294     public void copyOptionsFrom(Map<String, String> options) {
295         if (options.containsKey("-pretty")) {
296             pretty = (options.get("-pretty") != null);
297         }
298         if (options.containsKey("-verbose")) {
299             verbose = (options.get("-verbose") != null);
300         }
301         if (options.containsKey("-keepPath")) {
302             keepPath = (options.get("-keepPath") != null);
303         }
304         if (options.containsKey("-keepCP")) {
305             keepCP = (options.get("-keepCP") != null);
306         }
307         if (options.containsKey("-keepBytes")) {
308             keepBytes = (options.get("-keepBytes") != null);
309         }
310         if (options.containsKey("-parseBytes")) {
311             parseBytes = (options.get("-parseBytes") != null);
312         }
313         if (options.containsKey("-resolveRefs")) {
314             resolveRefs = (options.get("-resolveRefs") != null);
315         }
316         if (options.containsKey("-keepSizes")) {
317             keepSizes = (options.get("-keepSizes") != null);
318         }
319         if (options.containsKey("-keepOrder")) {
320             keepOrder = (options.get("-keepOrder") != null);
321         }
322         if (options.containsKey("-attrDef")) {
323             addAttrTypes(options.get("-attrDef").split(" "));
324         }
325         if (options.get("-jcov") != null) {
326             addJcovAttrTypes();
327         }
328     }
329 
330     public Element readFrom(InputStream in) throws IOException {
331         this.in = in;
332         // read the file header
333         int magic = u4();
334         if (magic != 0xCAFEBABE) {
335             throw new RuntimeException("bad magic number " + Integer.toHexString(magic));
336         }
337         cfile.setAttr("magic", "" + magic);
338         int minver = u2();
339         int majver = u2();
340         cfile.setAttr("minver", "" + minver);
341         cfile.setAttr("majver", "" + majver);
342         readCP();
343         readClass();
344         return result();
345     }
346 
347     public Element readFrom(File file) throws IOException {
348         InputStream in = null;
349         try {
350             in = new FileInputStream(file);
351             Element e = readFrom(new BufferedInputStream(in));
352             if (keepPath) {
353                 e.setAttr("path", file.toString());
354             }
355             return e;
356         } finally {
357             if (in != null) {
358                 in.close();
359             }
360         }
361     }
362 
363     private void readClass() throws IOException {
364         klass = new Element("Class");
365         cfile.add(klass);
366         int flags = u2();
367         String thisk = cpRef();
368         String superk = cpRef();
369         klass.setAttr("name", thisk);
370         boolean flagsSync = ((flags & Modifier.SYNCHRONIZED) != 0);
371         flags &= ~Modifier.SYNCHRONIZED;
372         String flagString = flagString(flags, klass);
373         if (!flagsSync) {
374             if (flagString.length() > 0) {
375                 flagString += " ";
376             }
377             flagString += "!synchronized";
378         }
379         klass.setAttr("flags", flagString);
380         klass.setAttr("super", superk);
381         for (int len = u2(), i = 0; i < len; i++) {
382             String interk = cpRef();
383             klass.add(new Element("Interface", "name", interk));
384         }
385         Element fields = readMembers("Field");
386         klass.addAll(fields);
387         Element methods = readMembers("Method");
388         if (!keepOrder) {
389             methods.sort();
390         }
391         klass.addAll(methods);
392         readAttributesFor(klass);
393         klass.trimToSize();
394         if (keepSizes) {
395             attachTo(cfile, formatAttrSizes());
396         }
397         if (paddingSize != 0) {
398             cfile.setAttr("padding", "" + paddingSize);
399         }
400     }
401 
402     private Element readMembers(String kind) throws IOException {
403         int len = u2();
404         Element members = new Element(len);
405         for (int i = 0; i < len; i++) {
406             Element member = new Element(kind);
407             int flags = u2();
408             String name = cpRef();
409             String type = cpRef();
410             member.setAttr("name", name);
411             member.setAttr("type", type);
412             member.setAttr("flags", flagString(flags, member));
413             readAttributesFor(member);
414             member.trimToSize();
415             members.add(member);
416         }
417         return members;
418     }
419 
420     protected String flagString(int flags, Element holder) {
421         // Superset of Modifier.toString.
422         int kind = 0;
423         if (holder.getName() == "Field") {
424             kind = 1;
425         }
426         if (holder.getName() == "Method") {
427             kind = 2;
428         }
429         StringBuffer sb = new StringBuffer();
430         for (int i = 0; flags != 0; i++, flags >>>= 1) {
431             if ((flags & 1) != 0) {
432                 if (sb.length() > 0) {
433                     sb.append(' ');
434                 }
435                 if (i < modifierNames.length) {
436                     String[] names = modifierNames[i];
437                     String name = (kind < names.length) ? names[kind] : null;
438                     for (String name2 : names) {
439                         if (name != null) {
440                             break;
441                         }
442                         name = name2;
443                     }
444                     sb.append(name);
445                 } else {
446                     sb.append("#").append(1 << i);
447                 }
448             }
449         }
450         return sb.toString();
451     }
452 
453     private void readAttributesFor(Element x) throws IOException {
454         Element prevCurrent;
455         Element y = new Element();
456         if (x.getName() == "Code") {
457             prevCurrent = currentCode;
458             currentCode = x;
459         } else {
460             prevCurrent = currentMember;
461             currentMember = x;
462         }
463         for (int len = u2(), i = 0; i < len; i++) {
464             int ref = u2();
465             String uname = cpName(ref).intern();
466             String refName = uname;
467             if (!resolveRefs) {
468                 refName = (REF_PREFIX + ref).intern();
469             }
470             String qname = (x.getName() + "." + uname).intern();
471             String wname = ("*." + uname).intern();
472             String type = attrTypes.get(qname);
473             if (type == null || "".equals(type)) {
474                 type = attrTypes.get(wname);
475             }
476             if ("".equals(type)) {
477                 type = null;
478             }
479             int size = u4();
480             int[] countVar = attrSizes.get(qname);
481             if (countVar == null) {
482                 attrSizes.put(qname, countVar = new int[2]);
483             }
484             countVar[0] += 1;
485             countVar[1] += size;
486             buf.reset();
487             for (int j = 0; j < size; j++) {
488                 buf.write(u1());
489             }
490             if (type == null && size == 0) {
491                 y.add(new Element(uname)); // <Bridge>, etc.
492             } else if (type == null) {
493                 //System.out.println("Warning:  No attribute type description: "+qname);
494                 // write cdata attribute
495                 Element a = new Element("Attribute",
496                         new String[]{"Name", refName},
497                         buf.toString(EIGHT_BIT_CHAR_ENCODING));
498                 a.addContent(getCPDigest());
499                 y.add(a);
500             } else if (type.equals("")) {
501                 // ignore this attribute...
502             } else {
503                 InputStream in0 = in;
504                 int fileSize0 = fileSize;
505                 ByteArrayInputStream in1 = new ByteArrayInputStream(buf.toByteArray());
506                 boolean ok = false;
507                 try {
508                     in = in1;
509                     // parse according to type desc.
510                     Element aval;
511                     if (type.equals("<Code>...")) {
512                         // delve into Code attribute
513                         aval = readCode();
514                     } else if (type.equals("<Frame>...")) {
515                         // delve into StackMap attribute
516                         aval = readStackMap(false);
517                     } else if (type.equals("<FrameX>...")) {
518                         // delve into StackMap attribute
519                         aval = readStackMap(true);
520                     } else if (type.startsWith("[")) {
521                         aval = readAttributeCallables(type);
522                     } else {
523                         aval = readAttribute(type);
524                     }
525                     //System.out.println("attachTo 1 "+y+" <- "+aval);
526                     attachTo(y, aval);
527                     if (false
528                             && in1.available() != 0) {
529                         throw new RuntimeException("extra bytes in " + qname + " :" + in1.available());
530                     }
531                     ok = true;
532                 } finally {
533                     in = in0;
534                     fileSize = fileSize0;
535                     if (!ok) {
536                         System.out.println("*** Failed to read " + type);
537                     }
538                 }
539             }
540         }
541         if (x.getName() == "Code") {
542             currentCode = prevCurrent;
543         } else {
544             currentMember = prevCurrent;
545         }
546         if (!keepOrder) {
547             y.sort();
548             y.sortAttrs();
549         }
550         //System.out.println("attachTo 2 "+x+" <- "+y);
551         attachTo(x, y);
552     }
553     private int fileSize = 0;
554     private int paddingSize = 0;
555     private HashMap<String, int[]> attrSizes = new HashMap<String, int[]>();
556 
557     private Element formatAttrSizes() {
558         Element e = new Element("Sizes");
559         e.setAttr("fileSize", "" + fileSize);
560         for (Map.Entry<String, int[]> ie : attrSizes.entrySet()) {
561             int[] countVar = ie.getValue();
562             e.add(new Element("AttrSize",
563                     "name", ie.getKey().toString(),
564                     "count", "" + countVar[0],
565                     "size", "" + countVar[1]));
566         }
567         return e;
568     }
569 
570     private void attachTo(Element x, Object aval0) {
571         if (aval0 == null) {
572             return;
573         }
574         //System.out.println("attachTo "+x+" : "+aval0);
575         if (!(aval0 instanceof Element)) {
576             x.add(aval0);
577             return;
578         }
579         Element aval = (Element) aval0;
580         if (!aval.isAnonymous()) {
581             x.add(aval);
582             return;
583         }
584         for (int imax = aval.attrSize(), i = 0; i < imax; i++) {
585             //%%
586             attachAttrTo(x, aval.getAttrName(i), aval.getAttr(i));
587         }
588         x.addAll(aval);
589     }
590 
591     private void attachAttrTo(Element x, String aname, String aval) {
592         //System.out.println("attachAttrTo "+x+" : "+aname+"="+aval);
593         String aval0 = x.getAttr(aname);
594         if (aval0 != null) {
595             aval = aval0 + " " + aval;
596         }
597         x.setAttr(aname, aval);
598     }
599 
600     private Element readAttributeCallables(String type) throws IOException {
601         assert (callables == null);
602         callables = getBodies(type);
603         Element res = readAttribute(callables[0]);
604         callables = null;
605         return res;
606     }
607 
608     private Element readAttribute(String type) throws IOException {
609         //System.out.println("readAttribute "+type);
610         Element aval = new Element();
611         String nextAttrName = null;
612         for (int len = type.length(), next, i = 0; i < len; i = next) {
613             String value;
614             switch (type.charAt(i)) {
615                 case '<':
616                     assert (nextAttrName == null);
617                     next = type.indexOf('>', ++i);
618                     String form = type.substring(i, next++);
619                     if (form.indexOf('=') < 0) {
620                         //  elem_placement = '<' elemname '>'
621                         assert (aval.attrSize() == 0);
622                         assert (aval.isAnonymous());
623                         aval.setName(form.intern());
624                     } else {
625                         //  attr_placement = '<' attrname '=' (value)? '>'
626                         int eqPos = form.indexOf('=');
627                         nextAttrName = form.substring(0, eqPos).intern();
628                         if (eqPos != form.length() - 1) {
629                             value = form.substring(eqPos + 1);
630                             attachAttrTo(aval, nextAttrName, value);
631                             nextAttrName = null;
632                         }
633                         // ...else subsequent type parsing will find the attr value
634                         // and add it as "nextAttrName".
635                     }
636                     continue;
637                 case '(':
638                     next = type.indexOf(')', ++i);
639                     int callee = Integer.parseInt(type.substring(i, next++));
640                     attachTo(aval, readAttribute(callables[callee]));
641                     continue;
642                 case 'N': // replication = 'N' int '[' type ... ']'
643                 {
644                     int count = getInt(type.charAt(i + 1), false);
645                     assert (count >= 0);
646                     next = i + 2;
647                     String type1 = getBody(type, next);
648                     next += type1.length() + 2;  // skip body and brackets
649                     for (int j = 0; j < count; j++) {
650                         attachTo(aval, readAttribute(type1));
651                     }
652                 }
653                 continue;
654                 case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']'
655                     int tagValue;
656                     if (type.charAt(++i) == 'S') {
657                         tagValue = getInt(type.charAt(++i), true);
658                     } else {
659                         tagValue = getInt(type.charAt(i), false);
660                     }
661                     attachAttrTo(aval, "tag", "" + tagValue);  // always named "tag"
662                     ++i;  // skip the int type char
663                     // union_case = '(' uc_tag (',' uc_tag)* ')' '[' body ']'
664                     // uc_tag = ('-')? digit+
665                     for (boolean foundCase = false;; i = next) {
666                         assert (type.charAt(i) == '(');
667                         next = type.indexOf(')', ++i);
668                         assert (next >= i);
669                         if (type.charAt(next - 1) == '\\'
670                                 && type.charAt(next - 2) != '\\') // Skip an escaped paren.
671                         {
672                             next = type.indexOf(')', next + 1);
673                         }
674                         String caseStr = type.substring(i, next++);
675                         String type1 = getBody(type, next);
676                         next += type1.length() + 2;  // skip body and brackets
677                         boolean lastCase = (caseStr.length() == 0);
678                         if (!foundCase
679                                 && (lastCase || matchTag(tagValue, caseStr))) {
680                             foundCase = true;
681                             // Execute this body.
682                             attachTo(aval, readAttribute(type1));
683                         }
684                         if (lastCase) {
685                             break;
686                         }
687                     }
688                     continue;
689                 case 'B':
690                 case 'H':
691                 case 'I': // int = oneof "BHI"
692                     next = i + 1;
693                     value = "" + getInt(type.charAt(i), false);
694                     break;
695                 case 'K':
696                     assert ("IJFDLQ".indexOf(type.charAt(i + 1)) >= 0);
697                     assert (type.charAt(i + 2) == 'H');  // only H works for now
698                     next = i + 3;
699                     value = cpRef();
700                     break;
701                 case 'R':
702                     assert ("CSDFMIU?".indexOf(type.charAt(i + 1)) >= 0);
703                     assert (type.charAt(i + 2) == 'H');  // only H works for now
704                     next = i + 3;
705                     value = cpRef();
706                     break;
707                 case 'P':  // bci = 'P' int
708                     next = i + 2;
709                     value = "" + getInt(type.charAt(i + 1), false);
710                     break;
711                 case 'S':  // signed_int = 'S' int
712                     next = i + 2;
713                     value = "" + getInt(type.charAt(i + 1), true);
714                     break;
715                 case 'F':
716                     next = i + 2;
717                     value = flagString(getInt(type.charAt(i + 1), false), currentMember);
718                     break;
719                 default:
720                     throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type);
721             }
722             // store the value
723             if (nextAttrName != null) {
724                 attachAttrTo(aval, nextAttrName, value);
725                 nextAttrName = null;
726             } else {
727                 attachTo(aval, value);
728             }
729         }
730         //System.out.println("readAttribute => "+aval);
731         assert (nextAttrName == null);
732         return aval;
733     }
734 
735     private int getInt(char ch, boolean signed) throws IOException {
736         if (signed) {
737             switch (ch) {
738                 case 'B':
739                     return (byte) u1();
740                 case 'H':
741                     return (short) u2();
742                 case 'I':
743                     return (int) u4();
744             }
745         } else {
746             switch (ch) {
747                 case 'B':
748                     return u1();
749                 case 'H':
750                     return u2();
751                 case 'I':
752                     return u4();
753             }
754         }
755         assert ("BHIJ".indexOf(ch) >= 0);
756         return 0;
757     }
758 
759     private Element readCode() throws IOException {
760         int stack = u2();
761         int local = u2();
762         int length = u4();
763         StringBuilder sb = new StringBuilder(length);
764         for (int i = 0; i < length; i++) {
765             sb.append((char) u1());
766         }
767         String bytecodes = sb.toString();
768         Element e = new Element("Code",
769                 "stack", "" + stack,
770                 "local", "" + local);
771         Element bytes = new Element("Bytes", (String[]) null, bytecodes);
772         if (keepBytes) {
773             e.add(bytes);
774         }
775         if (parseBytes) {
776             e.add(parseByteCodes(bytecodes));
777         }
778         for (int len = u2(), i = 0; i < len; i++) {
779             int start = u2();
780             int end = u2();
781             int catsh = u2();
782             String clasz = cpRef();
783             e.add(new Element("Handler",
784                     "start", "" + start,
785                     "end", "" + end,
786                     "catch", "" + catsh,
787                     "class", clasz));
788         }
789         readAttributesFor(e);
790         e.trimToSize();
791         return e;
792     }
793 
794     private Element parseByteCodes(String bytecodes) {
795         Element e = InstructionSyntax.parse(bytecodes);
796         for (Element ins : e.elements()) {
797             Number ref = ins.getAttrNumber("ref");
798             if (ref != null && resolveRefs) {
799                 int id = ref.intValue();
800                 String val = cpName(id);
801                 if (ins.getName().startsWith("ldc")) {
802                     // Yuck:  Arb. string cannot be an XML attribute.
803                     ins.add(val);
804                     val = "";
805                     byte tag = (id >= 0 && id < cpTag.length) ? cpTag[id] : 0;
806                     if (tag != 0) {
807                         ins.setAttrLong("tag", tag);
808                     }
809                 }
810                 if (ins.getName() == "invokeinterface"
811                         && computeInterfaceNum(val) == ins.getAttrLong("num")) {
812                     ins.setAttr("num", null);  // garbage bytes
813                 }
814                 ins.setAttr("ref", null);
815                 ins.setAttr("val", val);
816             }
817         }
818         return e;
819     }
820 
821     private Element readStackMap(boolean hasXOption) throws IOException {
822         Element result = new Element();
823         Element bytes = currentCode.findElement("Bytes");
824         assert (bytes != null && bytes.size() == 1);
825         int byteLength = ((String) bytes.get(0)).length();
826         boolean uoffsetIsU4 = (byteLength >= (1 << 16));
827         boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16);
828         boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16);
829         if (hasXOption || uoffsetIsU4 || ulocalvarIsU4 || ustackIsU4) {
830             Element flags = new Element("StackMapFlags");
831             if (hasXOption) {
832                 flags.setAttr("hasXOption", "true");
833             }
834             if (uoffsetIsU4) {
835                 flags.setAttr("uoffsetIsU4", "true");
836             }
837             if (ulocalvarIsU4) {
838                 flags.setAttr("ulocalvarIsU4", "true");
839             }
840             if (ustackIsU4) {
841                 flags.setAttr("ustackIsU4", "true");
842             }
843             currentCode.add(flags);
844         }
845         int frame_count = (uoffsetIsU4 ? u4() : u2());
846         for (int i = 0; i < frame_count; i++) {
847             int bci = (uoffsetIsU4 ? u4() : u2());
848             int flags = (hasXOption ? u1() : 0);
849             Element frame = new Element("Frame");
850             result.add(frame);
851             if (flags != 0) {
852                 frame.setAttr("flags", "" + flags);
853             }
854             frame.setAttr("bci", "" + bci);
855             // Scan local and stack types in this frame:
856             final int LOCALS = 0, STACK = 1;
857             for (int j = LOCALS; j <= STACK; j++) {
858                 int typeSize;
859                 if (j == LOCALS) {
860                     typeSize = (ulocalvarIsU4 ? u4() : u2());
861                 } else { // STACK
862                     typeSize = (ustackIsU4 ? u4() : u2());
863                 }
864                 Element types = new Element(j == LOCALS ? "Local" : "Stack");
865                 for (int k = 0; k < typeSize; k++) {
866                     int tag = u1();
867                     Element type = new Element(itemTagName(tag));
868                     types.add(type);
869                     switch (tag) {
870                         case ITEM_Object:
871                             type.setAttr("class", cpRef());
872                             break;
873                         case ITEM_Uninitialized:
874                         case ITEM_ReturnAddress:
875                             type.setAttr("bci", "" + (uoffsetIsU4 ? u4() : u2()));
876                             break;
877                     }
878                 }
879                 if (types.size() > 0) {
880                     frame.add(types);
881                 }
882             }
883         }
884         return result;
885     }
886 
887     private void readCP() throws IOException {
888         int cpLen = u2();
889         cpTag = new byte[cpLen];
890         cpName = new String[cpLen];
891         int cpTem[][] = new int[cpLen][];
892         for (int i = 1; i < cpLen; i++) {
893             cpTag[i] = (byte) u1();
894             switch (cpTag[i]) {
895                 case CONSTANT_Utf8:
896                     buf.reset();
897                     for (int len = u2(), j = 0; j < len; j++) {
898                         buf.write(u1());
899                     }
900                     cpName[i] = buf.toString(UTF8_ENCODING);
901                     break;
902                 case CONSTANT_Integer:
903                     cpName[i] = String.valueOf((int) u4());
904                     break;
905                 case CONSTANT_Float:
906                     cpName[i] = String.valueOf(Float.intBitsToFloat(u4()));
907                     break;
908                 case CONSTANT_Long:
909                     cpName[i] = String.valueOf(u8());
910                     i += 1;
911                     break;
912                 case CONSTANT_Double:
913                     cpName[i] = String.valueOf(Double.longBitsToDouble(u8()));
914                     i += 1;
915                     break;
916                 case CONSTANT_Class:
917                 case CONSTANT_String:
918                     cpTem[i] = new int[]{u2()};
919                     break;
920                 case CONSTANT_Fieldref:
921                 case CONSTANT_Methodref:
922                 case CONSTANT_InterfaceMethodref:
923                 case CONSTANT_NameAndType:
924                     cpTem[i] = new int[]{u2(), u2()};
925                     break;
926             }
927         }
928         for (int i = 1; i < cpLen; i++) {
929             switch (cpTag[i]) {
930                 case CONSTANT_Class:
931                 case CONSTANT_String:
932                     cpName[i] = cpName[cpTem[i][0]];
933                     break;
934                 case CONSTANT_NameAndType:
935                     cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]];
936                     break;
937             }
938         }
939         // do fieldref et al after nameandtype are all resolved
940         for (int i = 1; i < cpLen; i++) {
941             switch (cpTag[i]) {
942                 case CONSTANT_Fieldref:
943                 case CONSTANT_Methodref:
944                 case CONSTANT_InterfaceMethodref:
945                     cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]];
946                     break;
947             }
948         }
949         cpool = new Element("ConstantPool", cpName.length);
950         for (int i = 0; i < cpName.length; i++) {
951             if (cpName[i] == null) {
952                 continue;
953             }
954             cpool.add(new Element(cpTagName(cpTag[i]),
955                     new String[]{"id", "" + i},
956                     cpName[i]));
957         }
958         if (keepCP) {
959             cfile.add(cpool);
960         }
961     }
962 
963     private String cpRef() throws IOException {
964         int ref = u2();
965         if (resolveRefs) {
966             return cpName(ref);
967         } else {
968             return REF_PREFIX + ref;
969         }
970     }
971 
972     private String cpName(int id) {
973         if (id >= 0 && id < cpName.length) {
974             return cpName[id];
975         } else {
976             return "[CP#" + Integer.toHexString(id) + "]";
977         }
978     }
979 
980     private long u8() throws IOException {
981         return ((long) u4() << 32) + (((long) u4() << 32) >>> 32);
982     }
983 
984     private int u4() throws IOException {
985         return (u2() << 16) + u2();
986     }
987 
988     private int u2() throws IOException {
989         return (u1() << 8) + u1();
990     }
991 
992     private int u1() throws IOException {
993         int x = in.read();
994         if (x < 0) {
995             paddingSize++;
996             return 0;  // error recovery
997         }
998         fileSize++;
999         assert (x == (x & 0xFF));
1000         return x;
1001     }
1002 }
1003